05 Revisando decorators

Próxima Atividade
  • Sugerir melhoria
Carregando vídeo

Transcrição

Do jeito que nossa aplicação esta, se clicarmos muitas vezes em seguida no botão de importar negociações executaremos várias requisições para o servidor. Não há necessidade de tanta ansiedade por parte do usuário, mas para nos prevenirmos disso, podemos ignorar cliques que estejam dentro de uma janela de tempo definida pelo programador. No caso, se o usuário clicar em menos de meio segundo, cancelaremos a ação dele e iniciaremos uma nova. Em suma, essa solução utilizará um timer para processar as ações do usuário:

// app/ts/controllers/NegociacaoController.ts

import { NegociacoesView, MensagemView } from '../views/index';
import { Negociacao, Negociacoes } from '../models/index';
import { domInject } from '../helpers/decorators/index';
import { NegociacaoParcial } from '../models/index';

let timer = 0;

export class NegociacaoController {

   // código posterior omitido

    importaDados() {

        function isOk(res: Response) {

            if(res.ok) {
                return res;
            } else {
                throw new Error(res.statusText);
            }
        }

        clearTimeout(timer)
        timer = setTimeout(() => {

            fetch('http://localhost:8080/dados')
                .then(res => isOk(res))
                .then(res => res.json())
                .then((dados: NegociacaoParcial[]) => {
                    dados
                        .map(dado => new Negociacao(new Date(), dado.vezes, dado.montante))
                        .forEach(negociacao => this._negociacoes.adiciona(negociacao))
                    this._negociacoesView.update(this._negociacoes);
                })
                .catch(err => console.log(err));
        }, 500);

    }
}
// código posterior omitido

Excelente, nosso código funciona. Quando o usuário clicar pela primeira vez, qualquer temporizador que esteja sendo processado será cancelado e um novo será criado. É esse temporizador que executará nosso código. Se os meio segundos passarem, o temporizador executará nosso código. Então, se ele ficar clicando várias vezes em menos de meio segundo, apenas a última operação será executada.

No entanto esse código é muito invasivo, pois mistura sua lógica com a lógica do método do controller. Além disso, podemos querer usar a mesma estratégia em outros métodos da nossa aplicação. Aprendemos a criar decorators que isolam um código e que podem ser utilizados em vários lugares. Vamos criar o decorator chamado throttle, aliás, o padrão de projeto que lida com o problema que estamos enfrentando:

// app/ts/helpers/decorators/throttle.ts

export function throttle(milissegundos = 500) {

    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

        const metodoOriginal = descriptor.value;

        let timer = 0;

        descriptor.value = function(...args: any[]) {

            clearInterval(timer);
            timer = setTimeout(() => metodoOriginal.apply(this, args), milissegundos);
        }

        return descriptor;
    }
}
// app/ts/helpers/decorators/index.ts

export * from './logarTempoDeExecucao';
export * from './domInject';
export * from './throttle';

Agora, basta utilizarmos nosso decorator no método do nosso controller:

// app/ts/controllers/NegociacaoController.ts

import { NegociacoesView, MensagemView } from '../views/index';
import { Negociacao, Negociacoes } from '../models/index';
import { domInject, throttle } from '../helpers/decorators/index';
import { NegociacaoParcial } from '../models/index';

// não precisa mais da variável time!

export class NegociacaoController {

    // código anterior omitido 
    @throttle()
    importaDados() {

        function isOk(res: Response) {

            if (res.ok) {
                return res;
            } else {
                throw new Error(res.statusText);
            }
        }

        fetch('http://localhost:8080/dados')
            .then(res => isOk(res))
            .then(res => res.json())
            .then((dados: NegociacaoParcial[]) => {
                dados
                    .map(dado => new Negociacao(new Date(), dado.vezes, dado.montante))
                    .forEach(negociacao => this._negociacoes.adiciona(negociacao))
                this._negociacoesView.update(this._negociacoes);
            })
            .catch(err => console.log(err));


    }
}
// código posterior omitido

Contudo, se usarmos o mesmo decorator como método adiciona() de NegociacaoController teremos um problema. A página será recarregada. Isso acontece, porque como postergarmos a execução do nosso código, postergamos também event.preventDefault(). A boa notícia, é que podemos acessar event implicitamente dentro da chamada de qualquer função, sem necessariamente ele ter sido passado como parâmetro ou não para ela. Nesse sentido, vamos realizar sempre um event.preventDefault() em todos os métodos que usarem nosso decorator throttle:

// app/ts/helpers/decorators/throttle.ts

export function throttle(milissegundos = 500) {

    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

        const metodoOriginal = descriptor.value;

        let timer = 0;

        descriptor.value = function(...args: any[]) {
            if(event) event.preventDefault();
            clearInterval(timer);
            timer = setTimeout(() => metodoOriginal.apply(this, args), milissegundos);
        }

        return descriptor;
    }
}

Veja também que o TypeScript saber que event é uma variável implícita que pode existir ou não em uma função e por isso adota o tipo implícito Event, fantástico!

Tirar dúvida Próxima Atividade
ícone TypeScript parte 2: Mais técnicas e boas práticas

TypeScript parte 2: Mais técnicas e boas práticas

100%
Aula04 de 05

API externa e interface

Atividades 5 de 11

  • 01 API externa 05min
  • 02 Consumindo API externa 09min
  • 03 Definindo uma interface para a API 06min
  • 04 API externa
  • 05 Revisando decorators 11min
  • 06 Sobre decorators
  • 07 Isolando o acesso à API em um serviço 09min
  • 08 Interface de função 08min
  • 09 Sobre interface de funções
  • 10 Revisão 03min
  • 11 Consolidando seus conhecimentos

Aulas

Outros Links

Fórum Trocar Curso
Gravatar de Marcos Katsumi Kay

Marcos Katsumi Kay 34.0k xp

Logo da Alura